package base;

import static base.VertexState.ON;
import static base.VertexState.both;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.Map.Entry;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class ImmutableDynamic implements Dynamic {

	ImmutableMap<Integer,ImmutableSetMultimap<VertexState,SortedSet<Integer>>> dynamic;
	private ImmutableMap<SupportState,SupportState> map;
	
	public ImmutableSetMultimap<VertexState,SortedSet<Integer>> precursors(Integer which) {
		return dynamic.get(which);
	}
	
	public ImmutableSet<Integer> vertices() { return dynamic.keySet(); }
	
	public SupportState get(SupportState from) { return map.get(from); }
			
	private Set<SupportState> unTargetted;
	public Iterable<SupportState> unfixedSources() {
		if (unTargetted == null) {
			unTargetted = Sets.newHashSet(SupportState.allOfSize(dynamic.size()));
			unTargetted.removeAll(map.keySet());
		}
		return unTargetted;
	}
	
	@Override public String toString() {
		return Joiner.on("\n").withKeyValueSeparator(" => ").join(map);
	}
	
	public Set<SupportState> fixedSources() {
		return map.keySet();
	}
		
	public static Builder builder(int forNVertices) {
		return new Builder(forNVertices);
	}
	
	public static Builder builder(int forNVertices, int expectedSize) {
		return new Builder(forNVertices,expectedSize);
	}
	
	public static class Builder {
				
		public Builder(int forNVertices) { this(forNVertices,-1); }
		
		public Builder(int forNVertices, int expectedSize) {
			numVertices = forNVertices;
			
			map = expectedSize > 0 ? 
				Maps.<SupportState,SupportState>newHashMapWithExpectedSize(expectedSize) : 
				Maps.<SupportState,SupportState>newHashMap();
				
			map.put(SupportState.Zero.make(numVertices), SupportState.Zero.make(numVertices));
		}
		
		private boolean rightSize(SupportState input) {
			return input==null ? false : input.get().size()==numVertices;			
		}

		private final int numVertices;
		private final Map<SupportState,SupportState> map;
				
		public Builder put(Set<SupportState> froms, SupportState to) {
						
			if (rightSize(to)) throw new IllegalArgumentException();
			
			for (SupportState from : froms)
				if (!rightSize(from) || map.put(from, to)!=null)
					throw new IllegalArgumentException();
			
			return this;
		}
		
		public Builder put(SupportState...chain) {
			return put(Arrays.asList(chain));
		}
		
		public Builder put(List<SupportState> chain) {
			if (chain.size()<=1) return this;
			
			Iterator<SupportState> iter = chain.iterator();
			SupportState from;
			SupportState thrown;
			if (iter.hasNext() && rightSize(from = iter.next())) {
				SupportState to;
				while (iter.hasNext())
					if (!rightSize(to = iter.next()) ) {
						throw new IllegalArgumentException();
					} else if ((thrown=map.put(from, to))!=null) {
						throw new IllegalArgumentException(thrown.toString());
					} else { from = to; }
			}
			
			return this;
		}
		
		public Builder put(Map<SupportState,SupportState> map) {
			for (Entry<SupportState,SupportState> e : map.entrySet()) if (!e.getKey().get(ON).isEmpty()) put(e.getKey(),e.getValue());
			return this;
		}
		
		public ImmutableDynamic build() {
						
			Map<Integer,ImmutableSetMultimap.Builder<VertexState,SortedSet<Integer>>> builder = Maps.newHashMapWithExpectedSize(numVertices);
			ImmutableMap.Builder<SupportState, SupportState> mapBuilder = ImmutableMap.builder();
			for (Integer vertex=0; vertex < numVertices; vertex++) builder.put(vertex, ImmutableSetMultimap.<VertexState,SortedSet<Integer>>builder());
			for (SupportState key : map.keySet()) {//for all the before supportstates
				mapBuilder.put(key, map.get(key));
				for (VertexState v : both) // for the ON and OFF after vertexstates
					for (Integer vertex : map.get(key).get(v)) // for all the vertices that is that vertexstate 
						builder.get(vertex).put(v, key.get(ON)); //put for that vertex, vertexstate, the before supportstate
			}
			ImmutableDynamic result = new ImmutableDynamic();
			ImmutableMap.Builder<Integer, ImmutableSetMultimap<VertexState,SortedSet<Integer>>> resultBuilder = ImmutableMap.builder();
			for (Entry<Integer,ImmutableSetMultimap.Builder<VertexState,SortedSet<Integer>>> e : builder.entrySet()) resultBuilder.put(e.getKey(), e.getValue().build());
			result.dynamic = resultBuilder.build();
			result.map = mapBuilder.build();
			return result;
		}

		private final LinkedList<SupportState> chain = Lists.newLinkedList();
		public Builder putNext(SupportState supportState) {
			if (rightSize(supportState)) {
				chain.add(supportState);
			} else throw new IllegalArgumentException();
			if (chain.size() > 1) {
				put(chain.pop(),chain.peek());
			}
			return this;
		};
		
	}

	public int activity() {
		int activity = 0;
		for (SupportState s : map.keySet()) activity+=s.get(ON).size();
		return activity;
	}

	public static Builder builder(ImmutableDynamic on) {
		Builder result = new Builder(on.vertices().size());
		result.put(on.map);
		return result;
	}
	
}
